import { patchElementEffect, renderIframeReplaceApp } from './iframe';
import { renderElementToContainer } from './shadow';
import { pushUrlToWindow } from './sync';
import { documentProxyProperties, rawDocumentQuerySelector } from './common';
import { WUJIE_TIPS_RELOAD_DISABLED } from './constant';
import { anchorElementGenerator, getIframe, isCallable, warn } from './utils';

/**
 * location href 的set劫持操作
 */
function locationHrefSet(
  iframe: HTMLIFrameElement,
  value: string,
  appHostPath: string
): boolean {
  const { id, document } = iframe.contentWindow.__WUJIE;
  let url = value;
  if (!/^http/.test(url)) {
    let hrefElement = anchorElementGenerator(url);
    url =
      appHostPath +
      hrefElement.pathname +
      hrefElement.search +
      hrefElement.hash;
    hrefElement = null;
  }
  iframe.contentWindow.__WUJIE.hrefFlag = true;
  const iframeBody = rawDocumentQuerySelector.call(
    iframe.contentDocument,
    'body'
  );
  renderElementToContainer(document.documentElement, iframeBody);
  renderIframeReplaceApp(
    window.decodeURIComponent(url),
    getIframe(id).parentElement
  );

  pushUrlToWindow(id, url);
  return true;
}

/**
 * 降级情况下document、location代理处理
 */
export function localGenerator(
  iframe: HTMLIFrameElement,
  urlElement: HTMLAnchorElement,
  mainHostPath: string,
  appHostPath: string
): {
  proxyDocument: Object;
  proxyLocation: Object;
} {
  // 代理 document
  const proxyDocument = {};
  const sandbox = iframe.contentWindow.__WUJIE;

  // 特殊处理
  Object.defineProperties(proxyDocument, {
    createElement: {
      get: () => {
        return function (...args) {
          const element =
            iframe.contentWindow.__WUJIE_RAW_DOCUMENT_CREATE_ELEMENT__.apply(
              iframe.contentDocument,
              args
            );
          patchElementEffect(element, iframe.contentWindow);
          return element;
        };
      }
    },
    createTextNode: {
      get: () => {
        return function (...args) {
          const element =
            iframe.contentWindow.__WUJIE_RAW_DOCUMENT_CREATE_TEXT_NODE__.apply(
              iframe.contentDocument,
              args
            );
          patchElementEffect(element, iframe.contentWindow);
          return element;
        };
      }
    },
    documentURI: {
      get: () => (sandbox.proxyLocation as Location).href
    },
    URL: {
      get: () => (sandbox.proxyLocation as Location).href
    },
    getElementsByTagName: {
      get() {
        return function (...args) {
          const tagName = args[0];
          if (tagName === 'script') {
            return iframe.contentDocument.scripts as any;
          }
          return sandbox.document.getElementsByTagName(tagName) as any;
        };
      }
    }
  });
  // 普通处理
  const {
    modifyLocalProperties,
    modifyProperties,
    ownerProperties,
    shadowProperties,
    shadowMethods,
    documentProperties,
    documentMethods
  } = documentProxyProperties;
  modifyProperties
    .filter(key => !modifyLocalProperties.includes(key))
    .concat(
      ownerProperties,
      shadowProperties,
      shadowMethods,
      documentProperties,
      documentMethods
    )
    .forEach(key => {
      Object.defineProperty(proxyDocument, key, {
        get: () => {
          const value = sandbox.document?.[key];
          return isCallable(value) ? value.bind(sandbox.document) : value;
        }
      });
    });

  // 代理 location
  const proxyLocation = {};
  const location = iframe.contentWindow.location;
  const locationKeys = Object.keys(location);
  const constantKey = ['host', 'hostname', 'port', 'protocol', 'port'];
  constantKey.forEach(key => {
    proxyLocation[key] = urlElement[key];
  });
  Object.defineProperties(proxyLocation, {
    href: {
      get: () => location.href.replace(mainHostPath, appHostPath),
      set: value => {
        locationHrefSet(iframe, value, appHostPath);
      }
    },
    reload: {
      get() {
        warn(WUJIE_TIPS_RELOAD_DISABLED);
        return () => null;
      }
    }
  });
  locationKeys
    .filter(key => !constantKey.concat(['href', 'reload']).includes(key))
    .forEach(key => {
      Object.defineProperty(proxyLocation, key, {
        get: () =>
          isCallable(location[key])
            ? location[key].bind(location)
            : location[key]
      });
    });
  return { proxyDocument, proxyLocation };
}
